/*
 ------------------------------------------------------------------------------
	mad - a test program for using mad library on Linux.

	Copyright (C) 2011 Pierre Veber
	
	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 3 of the License, or
	(at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software Foundation,
	Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
------------------------------------------------------------------------------
*/

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <malloc.h>
#include <pthread.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <mad.h>
#include <alsa/asoundlib.h>

typedef struct {
	FILE* fp;
	char* szFilePath;
	struct mad_decoder decoder;
	snd_pcm_t* pAudio;
} decode_thread_data_t;

#define READ_BUF_SIZE		8192
#define CMD_QUIT	0x71

static unsigned char _read_buf[READ_BUF_SIZE];

/*
 * The following utility routine performs simple rounding, clipping, and
 * scaling of MAD's high-resolution samples down to 16 bits. It does not
 * perform any dithering or noise shaping, which would be recommended to
 * obtain any exceptional audio quality. It is therefore not recommended to
 * use this routine if high-quality output is desired.
 */

static inline signed int scale(mad_fixed_t sample)
{
	/* round */
	sample += (1L << (MAD_F_FRACBITS - 16));

	/* clip */
	if (sample >= MAD_F_ONE)
	{
		sample = MAD_F_ONE - 1;
	}
	else if (sample < -MAD_F_ONE)
	{
		sample = -MAD_F_ONE;
	}

	/* quantize */
	return sample >> (MAD_F_FRACBITS + 1 - 16);
}

/*
 * =====================================================================
 * Decoding MAD Callbacks
 * 
 * 
 * =====================================================================
 */

/*
 * This is the input callback. The purpose of this callback is to (re)fill
 * the stream buffer which is to be decoded. In this example, an entire file
 * has been mapped into memory, so we just call mad_stream_buffer() with the
 * address and length of the mapping. When this callback is called a second
 * time, we are finished decoding.
 */

enum mad_flow fMadInputCallback(void *pData, struct mad_stream *pStream)
{
	size_t read, to_read = READ_BUF_SIZE;
	decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)pData;
	
	// move remaining frames to beginning of buffer
	if (pStream->next_frame != NULL)
	{
		to_read = pStream->next_frame - _read_buf;
		memmove(_read_buf, pStream->next_frame, READ_BUF_SIZE - to_read);
	}
	read = fread(_read_buf + (READ_BUF_SIZE - to_read), 1, to_read, pDecodeThreadData->fp);
	if (read + READ_BUF_SIZE - to_read > 0)
	{
		mad_stream_buffer(
						pStream,
						(const unsigned char*)_read_buf,
						read + READ_BUF_SIZE - to_read
						);
		return MAD_FLOW_CONTINUE;
	}
	else
	{
		return MAD_FLOW_STOP;
	}
}

/*
 * This is the output callback function. It is called after each frame of
 * MPEG audio data has been completely decoded. The purpose of this callback
 * is to output (or play) the decoded PCM audio.
 */

static enum mad_flow fMadOutputCallback(void* pData, struct mad_header const *pHeader, struct mad_pcm *pPcm)
{
	char _buf[4];
	unsigned int nchannels, nsamples;
	mad_fixed_t const *left_ch, *right_ch;
	signed int sample;
	int rc;

	decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)pData;
  
	nchannels = pPcm->channels;
	nsamples  = pPcm->length;

	left_ch   = pPcm->samples[0];
	right_ch  = pPcm->samples[1];
  
	while (nsamples--)
	{
		/* output sample(s) in 16-bit signed little-endian PCM */
		sample = scale(*left_ch++);
		_buf[0] = (sample >> 0) & 0xff;
		_buf[1] = (sample >> 8) & 0xff;
		sample = scale(*right_ch++);
		_buf[2] = (sample >> 0) & 0xff;
		_buf[3] = (sample >> 8) & 0xff;

		rc = snd_pcm_writei(pDecodeThreadData->pAudio, _buf, 1);
		if (rc == -EPIPE)
		{
			/* EPIPE means underrun */
			fprintf(stderr, "underrun occurred\n");
			snd_pcm_prepare(pDecodeThreadData->pAudio);
		}
		else if (rc < 0)
		{
			fprintf(stderr, "error from writei: %s\n", snd_strerror(rc));
		}
		else if (rc != (int)1)
		{
			fprintf(stderr, "short write, write %d frames\n", rc);
		}
	}
	
	return MAD_FLOW_CONTINUE;
}

/*
 * This is the error callback function. It is called whenever a decoding
 * error occurs. The error is indicated by stream->error; the list of
 * possible MAD_ERROR_* errors can be found in the mad.h (or
 * libmad/stream.h) header file.
 */

static
enum mad_flow fMadErrorCallback(void* pData, struct mad_stream* pStream, struct mad_frame* pFrame)
{
	fprintf(stderr, "decoding error 0x%04x (%s)\n", pStream->error, mad_stream_errorstr(pStream));

	/* return MAD_FLOW_BREAK here to stop decoding (and propagate an error) */

	return MAD_FLOW_CONTINUE;
}

/*
 * =====================================================================
 * Alsa routines
 * 
 * 
 * =====================================================================
 */

static int fAlsaInit(snd_pcm_t** ppHandle)
{
	/* initialize alsa */
	int rc;
	int dir;
	snd_pcm_hw_params_t *params;
	unsigned int val;
	snd_pcm_uframes_t frames;

	/* Open PCM device for playback. */
	rc = snd_pcm_open(ppHandle, "default", SND_PCM_STREAM_PLAYBACK, 0);
	if (rc < 0)
	{
		fprintf(stderr, "unable to open pcm device: %s\n", snd_strerror(rc));
		return rc;
	}

	/* Allocate a hardware parameters object. */
	snd_pcm_hw_params_alloca(&params);

	/* Fill it in with default values. */
	snd_pcm_hw_params_any(*ppHandle, params);

	/* Set the desired hardware parameters. */

	/* Interleaved mode */
	snd_pcm_hw_params_set_access(*ppHandle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

	/* Signed 16-bit little-endian format */
	snd_pcm_hw_params_set_format(*ppHandle, params, SND_PCM_FORMAT_S16_LE);

	/* Two channels (stereo) */
	snd_pcm_hw_params_set_channels(*ppHandle, params, 2);

	/* 44100 bits/second sampling rate (CD quality) */
	val = 44100;
	snd_pcm_hw_params_set_rate_near(*ppHandle, params, &val, &dir);

	/* Set period size to MAD frames. */
	frames = 1152;
	snd_pcm_hw_params_set_period_size_near(*ppHandle, params, &frames, &dir);

	/* Write the parameters to the driver */
	rc = snd_pcm_hw_params(*ppHandle, params);
	if (rc < 0) 
	{
		fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(rc));
		return rc;
	}

	return 0;
}

/*
 * =====================================================================
 * Decoding thread
 * 
 * 
 * =====================================================================
 */

void fDecodeThreadCleanup(void* pData)
{
	decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)pData;

	printf("\tfDecodeThreadCleanup\n");
	
	/* clean mad decoder */
	mad_decoder_finish(&pDecodeThreadData->decoder);

	/* close file */
	if (pDecodeThreadData->fp != NULL)
	{
		fclose(pDecodeThreadData->fp);
	}
	
	/* free audio */
	snd_pcm_drain(pDecodeThreadData->pAudio);
	snd_pcm_close(pDecodeThreadData->pAudio);

}

void* fDecodeThread(void* pData)
{
	//struct stat stat;
	//int fd;
	int result;
	decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)pData;

	/* register thread cleanup func */
	pthread_cleanup_push(fDecodeThreadCleanup,pData);

	/* open file */
	pDecodeThreadData->fp = fopen(pDecodeThreadData->szFilePath,"rb");
	if (pDecodeThreadData->fp == NULL)
	{
		fprintf(stderr, "\tfDecodeThread: could not open file %s (err %d)\n", pDecodeThreadData->szFilePath, errno);
		goto decode_exit;
	}

	/* configure input, output, and error functions */
	mad_decoder_init(
				&pDecodeThreadData->decoder,
				pDecodeThreadData,
        fMadInputCallback,
        0 /* header */,
        0 /* filter */,
        fMadOutputCallback,
        fMadErrorCallback,
        0 /* message */
        );

	fAlsaInit(&pDecodeThreadData->pAudio);

	/* start decoding */
	result = mad_decoder_run(&pDecodeThreadData->decoder, MAD_DECODER_MODE_SYNC);
	
decode_exit:
	/* release the decoder */
	pthread_cleanup_pop(1);
	pthread_exit(NULL);
}

int main(int argc, char* argv[])
{
    pthread_t tDecodeThread;
    
    void* pDecodeThreadExit;
    
    decode_thread_data_t* pDecodeThreadData = (decode_thread_data_t*)malloc(sizeof(decode_thread_data_t));
    
    if (argc > 1)
    {
    
		pDecodeThreadData->fp = NULL;
		pDecodeThreadData->szFilePath = strdup(argv[1]);

		printf("========================================================\n");
		printf("This is Willy-player/PROOF/LEVEL1.\n");
		printf("========================================================\n");
		printf("This program is a simple libmad and alsa application \n");
		printf("which plays a mp3 file.\n");
		printf("Enter q to quit at any time: ");
	  
		/* init of decode thread */
		if (pthread_create(&tDecodeThread,NULL,fDecodeThread,pDecodeThreadData) != 0)
		{
			fprintf(stderr, "Error creating decode thread!\n");
			return 1;
		}

		/* main loop */
		while (1)
		{
			int res = fgetc(stdin);
			if ((res == EOF) || (res == CMD_QUIT))
			{
				/* perform clean-up */
				pthread_cancel(tDecodeThread);
				break;
			}
		}
		
		printf( "waiting for Decode thread to exit...\n");
		pthread_join(tDecodeThread, &pDecodeThreadExit);
		if (pDecodeThreadExit != PTHREAD_CANCELED)
		{
			//printf("Decode return value: %x\n", pDecodeThreadExit);
		}
		else
		{
			printf("Decode thread was cancelled.\n");
		}
	
	}
	else
	{
		printf("========================================================\n");
		printf("This is Willy-player/PROOF/LEVEL1.\n");
		printf("========================================================\n");
		printf("Please specify a path to mp3 file! \n");
	}	
	
	free(pDecodeThreadData);
	printf("========================================================\n");
	
    return 0;
}
